package com.ctrlspace.core.compiler;

import java.util.ArrayList;
import java.util.List;

import com.sun.source.tree.ArrayAccessTree;
import com.sun.source.tree.ArrayTypeTree;
import com.sun.source.tree.AssignmentTree;
import com.sun.source.tree.CaseTree;
import com.sun.source.tree.CatchTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.CompoundAssignmentTree;
import com.sun.source.tree.ConditionalExpressionTree;
import com.sun.source.tree.DoWhileLoopTree;
import com.sun.source.tree.EnhancedForLoopTree;
import com.sun.source.tree.ErroneousTree;
import com.sun.source.tree.ExpressionStatementTree;
import com.sun.source.tree.ForLoopTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.IfTree;
import com.sun.source.tree.ImportTree;
import com.sun.source.tree.LabeledStatementTree;
import com.sun.source.tree.LiteralTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.NewArrayTree;
import com.sun.source.tree.ParameterizedTypeTree;
import com.sun.source.tree.ParenthesizedTree;
import com.sun.source.tree.ReturnTree;
import com.sun.source.tree.SwitchTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.TryTree;
import com.sun.source.tree.TypeParameterTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.tree.WhileLoopTree;
import com.sun.source.util.SourcePositions;
import com.sun.source.util.TreePath;
import com.sun.source.util.TreePathScanner;
import com.sun.source.util.Trees;

public class CodeAnalyzerTreeVisitor extends TreePathScanner<Object, Trees> {

	private final String variableName;
	private long targetLineNumber;
	private String currentMethod = "class";
	private List<Variable> listOfVariables = new ArrayList<Variable>();
	private String dataType;

	public CodeAnalyzerTreeVisitor(int targetLineNumber, String variableName, boolean found) {
		this.targetLineNumber = targetLineNumber;
		this.variableName = variableName;
	}

	public Object visitClass(ClassTree classTree, Trees trees) {
		return super.visitClass(classTree, trees);
	}

	public Object visitMethod(MethodTree methodTree, Trees trees) {
//		TreePath path = getCurrentPath();
		if (methodTree.getName().toString().equals("<init>")) {
			this.currentMethod = "class";
		} else {
			this.currentMethod = methodTree.getName().toString();
		}
		return super.visitMethod(methodTree, trees);
	}

	@Override
	public Object visitVariable(VariableTree variableTree, Trees trees) {
		TreePath path = getCurrentPath();
		CompilationUnitTree compileTree = path.getCompilationUnit();
		SourcePositions sp = trees.getSourcePositions();
		long startIndex = sp.getStartPosition(compileTree, variableTree);
		long currentLineNumber = compileTree.getLineMap().getLineNumber(startIndex);
		List<? extends ImportTree> imports = compileTree.getImports();

		Variable var = new Variable();
		var.setVariableName(variableTree.getName().toString());

		boolean found = false;
		for (ImportTree it : imports) {
			String s = it.getQualifiedIdentifier().toString();
			String type = variableTree.getType().toString();
			if (s.contains(type)) {
				var.setType(it.getQualifiedIdentifier().toString());
				found = true;
			} else if (type.contains("<") && type.contains(">")) {
				int first = type.indexOf("<");
				int second = type.indexOf(">");
				type = s.substring(first, second);
				if (s.contains(type)) {
					var.setType(it.getQualifiedIdentifier().toString());
					found = true;
				}
			}
		}
		if (found == false) {
			String type = variableTree.getType().toString();
			if (type.equals("int") || type.equals("float") || type.equals("String") || type.equals("double") || type.equals("long") || type.equals("boolean") || type.equals("char") || type.equals("byte") || type.equals("short")) {
				var.setType("java.lang." + type);
			} else if (compileTree.getPackageName() != null) {
				var.setType(compileTree.getPackageName().toString() + "." + type);
			}
		}
		var.setMethod(this.currentMethod);
		var.setLineNumber(currentLineNumber);
		listOfVariables.add(var);

		return super.visitVariable(variableTree, trees);
	}

	@Override
	public Object visitAssignment(AssignmentTree assignmentTree, Trees trees) {
		TreePath path = getCurrentPath();
		CompilationUnitTree compileTree = path.getCompilationUnit();
		SourcePositions sp = trees.getSourcePositions();
		long startIndex = sp.getStartPosition(compileTree, assignmentTree);
		long currentLineNumber = compileTree.getLineMap().getLineNumber(startIndex);

		if (currentLineNumber == this.targetLineNumber) {
			getDataType();
		}

		return super.visitAssignment(assignmentTree, trees);
	}

	@Override
	public Object visitErroneous(ErroneousTree erroneousTree, Trees trees) {
		TreePath path = getCurrentPath();
		CompilationUnitTree compileTree = path.getCompilationUnit();
		SourcePositions sp = trees.getSourcePositions();
		long startIndex = sp.getStartPosition(compileTree, erroneousTree);
		long currentLineNumber = compileTree.getLineMap().getLineNumber(startIndex);

		if (currentLineNumber == this.targetLineNumber) {
			getDataType();
		}

		return super.visitErroneous(erroneousTree, trees);
	}

	@Override
	public Object visitArrayAccess(ArrayAccessTree arg0, Trees arg1) {
		TreePath path = getCurrentPath();
		CompilationUnitTree compileTree = path.getCompilationUnit();
		SourcePositions sp = arg1.getSourcePositions();
		long startIndex = sp.getStartPosition(compileTree, arg0);
		long currentLineNumber = compileTree.getLineMap().getLineNumber(startIndex);

		if (currentLineNumber == this.targetLineNumber) {
			getDataType();
		}
		return super.visitArrayAccess(arg0, arg1);
	}

	@Override
	public Object visitArrayType(ArrayTypeTree arg0, Trees arg1) {
		TreePath path = getCurrentPath();
		CompilationUnitTree compileTree = path.getCompilationUnit();
		SourcePositions sp = arg1.getSourcePositions();
		long startIndex = sp.getStartPosition(compileTree, arg0);
		long currentLineNumber = compileTree.getLineMap().getLineNumber(startIndex);

		if (currentLineNumber == this.targetLineNumber) {
			getDataType();
		}
		return super.visitArrayType(arg0, arg1);
	}

	@Override
	public Object visitCase(CaseTree arg0, Trees arg1) {
		TreePath path = getCurrentPath();
		CompilationUnitTree compileTree = path.getCompilationUnit();
		SourcePositions sp = arg1.getSourcePositions();
		long startIndex = sp.getStartPosition(compileTree, arg0);
		long currentLineNumber = compileTree.getLineMap().getLineNumber(startIndex);

		if (currentLineNumber == this.targetLineNumber) {
			getDataType();
		}
		return super.visitCase(arg0, arg1);
	}

	@Override
	public Object visitCatch(CatchTree arg0, Trees arg1) {
		TreePath path = getCurrentPath();
		CompilationUnitTree compileTree = path.getCompilationUnit();
		SourcePositions sp = arg1.getSourcePositions();
		long startIndex = sp.getStartPosition(compileTree, arg0);
		long currentLineNumber = compileTree.getLineMap().getLineNumber(startIndex);

		if (currentLineNumber == this.targetLineNumber) {
			getDataType();
		}
		return super.visitCatch(arg0, arg1);
	}

	@Override
	public Object visitCompoundAssignment(CompoundAssignmentTree arg0, Trees arg1) {
		TreePath path = getCurrentPath();
		CompilationUnitTree compileTree = path.getCompilationUnit();
		SourcePositions sp = arg1.getSourcePositions();
		long startIndex = sp.getStartPosition(compileTree, arg0);
		long currentLineNumber = compileTree.getLineMap().getLineNumber(startIndex);

		if (currentLineNumber == this.targetLineNumber) {
			getDataType();
		}
		return super.visitCompoundAssignment(arg0, arg1);
	}

	@Override
	public Object visitConditionalExpression(ConditionalExpressionTree arg0, Trees arg1) {
		TreePath path = getCurrentPath();
		CompilationUnitTree compileTree = path.getCompilationUnit();
		SourcePositions sp = arg1.getSourcePositions();
		long startIndex = sp.getStartPosition(compileTree, arg0);
		long currentLineNumber = compileTree.getLineMap().getLineNumber(startIndex);

		if (currentLineNumber == this.targetLineNumber) {
			getDataType();
		}
		return super.visitConditionalExpression(arg0, arg1);
	}

	@Override
	public Object visitDoWhileLoop(DoWhileLoopTree arg0, Trees arg1) {
		TreePath path = getCurrentPath();
		CompilationUnitTree compileTree = path.getCompilationUnit();
		SourcePositions sp = arg1.getSourcePositions();
		long startIndex = sp.getStartPosition(compileTree, arg0);
		long currentLineNumber = compileTree.getLineMap().getLineNumber(startIndex);

		if (currentLineNumber == this.targetLineNumber) {
			getDataType();
		}
		return super.visitDoWhileLoop(arg0, arg1);
	}

	@Override
	public Object visitEnhancedForLoop(EnhancedForLoopTree arg0, Trees arg1) {
		TreePath path = getCurrentPath();
		CompilationUnitTree compileTree = path.getCompilationUnit();
		SourcePositions sp = arg1.getSourcePositions();
		long startIndex = sp.getStartPosition(compileTree, arg0);
		long currentLineNumber = compileTree.getLineMap().getLineNumber(startIndex);

		if (currentLineNumber == this.targetLineNumber) {
			getDataType();
		}
		return super.visitEnhancedForLoop(arg0, arg1);
	}

	@Override
	public Object visitExpressionStatement(ExpressionStatementTree arg0, Trees arg1) {
		TreePath path = getCurrentPath();
		CompilationUnitTree compileTree = path.getCompilationUnit();
		SourcePositions sp = arg1.getSourcePositions();
		long startIndex = sp.getStartPosition(compileTree, arg0);
		long currentLineNumber = compileTree.getLineMap().getLineNumber(startIndex);

		if (currentLineNumber == this.targetLineNumber) {
			getDataType();
		}
		return super.visitExpressionStatement(arg0, arg1);
	}

	@Override
	public Object visitForLoop(ForLoopTree arg0, Trees arg1) {
		TreePath path = getCurrentPath();
		CompilationUnitTree compileTree = path.getCompilationUnit();
		SourcePositions sp = arg1.getSourcePositions();
		long startIndex = sp.getStartPosition(compileTree, arg0);
		long currentLineNumber = compileTree.getLineMap().getLineNumber(startIndex);

		if (currentLineNumber == this.targetLineNumber) {
			getDataType();
		}
		return super.visitForLoop(arg0, arg1);
	}

	@Override
	public Object visitIdentifier(IdentifierTree arg0, Trees arg1) {
		TreePath path = getCurrentPath();
		CompilationUnitTree compileTree = path.getCompilationUnit();
		SourcePositions sp = arg1.getSourcePositions();
		long startIndex = sp.getStartPosition(compileTree, arg0);
		long currentLineNumber = compileTree.getLineMap().getLineNumber(startIndex);

		if (currentLineNumber == this.targetLineNumber) {
			getDataType();
		}
		return super.visitIdentifier(arg0, arg1);
	}

	@Override
	public Object visitIf(IfTree arg0, Trees arg1) {
		TreePath path = getCurrentPath();
		CompilationUnitTree compileTree = path.getCompilationUnit();
		SourcePositions sp = arg1.getSourcePositions();
		long startIndex = sp.getStartPosition(compileTree, arg0);
		long currentLineNumber = compileTree.getLineMap().getLineNumber(startIndex);

		if (currentLineNumber == this.targetLineNumber) {
			getDataType();
		}
		return super.visitIf(arg0, arg1);
	}

	@Override
	public Object visitLabeledStatement(LabeledStatementTree arg0, Trees arg1) {
		TreePath path = getCurrentPath();
		CompilationUnitTree compileTree = path.getCompilationUnit();
		SourcePositions sp = arg1.getSourcePositions();
		long startIndex = sp.getStartPosition(compileTree, arg0);
		long currentLineNumber = compileTree.getLineMap().getLineNumber(startIndex);

		if (currentLineNumber == this.targetLineNumber) {
			getDataType();
		}
		return super.visitLabeledStatement(arg0, arg1);
	}

	@Override
	public Object visitLiteral(LiteralTree arg0, Trees arg1) {
		TreePath path = getCurrentPath();
		CompilationUnitTree compileTree = path.getCompilationUnit();
		SourcePositions sp = arg1.getSourcePositions();
		long startIndex = sp.getStartPosition(compileTree, arg0);
		long currentLineNumber = compileTree.getLineMap().getLineNumber(startIndex);

		if (currentLineNumber == this.targetLineNumber) {
			getDataType();
		}
		return super.visitLiteral(arg0, arg1);
	}

	@Override
	public Object visitMemberSelect(MemberSelectTree arg0, Trees arg1) {
		TreePath path = getCurrentPath();
		CompilationUnitTree compileTree = path.getCompilationUnit();
		SourcePositions sp = arg1.getSourcePositions();
		long startIndex = sp.getStartPosition(compileTree, arg0);
		long currentLineNumber = compileTree.getLineMap().getLineNumber(startIndex);

		if (currentLineNumber == this.targetLineNumber) {
			getDataType();
		}
		return super.visitMemberSelect(arg0, arg1);
	}

	@Override
	public Object visitMethodInvocation(MethodInvocationTree arg0, Trees arg1) {
		TreePath path = getCurrentPath();
		CompilationUnitTree compileTree = path.getCompilationUnit();
		SourcePositions sp = arg1.getSourcePositions();
		long startIndex = sp.getStartPosition(compileTree, arg0);
		long currentLineNumber = compileTree.getLineMap().getLineNumber(startIndex);

		if (currentLineNumber == this.targetLineNumber) {
			getDataType();
		}
		return super.visitMethodInvocation(arg0, arg1);
	}

	@Override
	public Object visitNewArray(NewArrayTree arg0, Trees arg1) {
		TreePath path = getCurrentPath();
		CompilationUnitTree compileTree = path.getCompilationUnit();
		SourcePositions sp = arg1.getSourcePositions();
		long startIndex = sp.getStartPosition(compileTree, arg0);
		long currentLineNumber = compileTree.getLineMap().getLineNumber(startIndex);

		if (currentLineNumber == this.targetLineNumber) {
			getDataType();
		}
		return super.visitNewArray(arg0, arg1);
	}

	@Override
	public Object visitOther(Tree arg0, Trees arg1) {
		TreePath path = getCurrentPath();
		CompilationUnitTree compileTree = path.getCompilationUnit();
		SourcePositions sp = arg1.getSourcePositions();
		long startIndex = sp.getStartPosition(compileTree, arg0);
		long currentLineNumber = compileTree.getLineMap().getLineNumber(startIndex);

		if (currentLineNumber == this.targetLineNumber) {
			getDataType();
		}
		return super.visitOther(arg0, arg1);
	}

	@Override
	public Object visitParameterizedType(ParameterizedTypeTree arg0, Trees arg1) {
		TreePath path = getCurrentPath();
		CompilationUnitTree compileTree = path.getCompilationUnit();
		SourcePositions sp = arg1.getSourcePositions();
		long startIndex = sp.getStartPosition(compileTree, arg0);
		long currentLineNumber = compileTree.getLineMap().getLineNumber(startIndex);

		if (currentLineNumber == this.targetLineNumber) {
			getDataType();
		}
		return super.visitParameterizedType(arg0, arg1);
	}

	@Override
	public Object visitParenthesized(ParenthesizedTree arg0, Trees arg1) {
		TreePath path = getCurrentPath();
		CompilationUnitTree compileTree = path.getCompilationUnit();
		SourcePositions sp = arg1.getSourcePositions();
		long startIndex = sp.getStartPosition(compileTree, arg0);
		long currentLineNumber = compileTree.getLineMap().getLineNumber(startIndex);

		if (currentLineNumber == this.targetLineNumber) {
			getDataType();
		}
		return super.visitParenthesized(arg0, arg1);
	}

	@Override
	public Object visitReturn(ReturnTree arg0, Trees arg1) {
		TreePath path = getCurrentPath();
		CompilationUnitTree compileTree = path.getCompilationUnit();
		SourcePositions sp = arg1.getSourcePositions();
		long startIndex = sp.getStartPosition(compileTree, arg0);
		long currentLineNumber = compileTree.getLineMap().getLineNumber(startIndex);

		if (currentLineNumber == this.targetLineNumber) {
			getDataType();
		}
		return super.visitReturn(arg0, arg1);
	}

	@Override
	public Object visitSwitch(SwitchTree arg0, Trees arg1) {
		TreePath path = getCurrentPath();
		CompilationUnitTree compileTree = path.getCompilationUnit();
		SourcePositions sp = arg1.getSourcePositions();
		long startIndex = sp.getStartPosition(compileTree, arg0);
		long currentLineNumber = compileTree.getLineMap().getLineNumber(startIndex);

		if (currentLineNumber == this.targetLineNumber) {
			getDataType();
		}
		return super.visitSwitch(arg0, arg1);
	}

	@Override
	public Object visitTry(TryTree arg0, Trees arg1) {
		TreePath path = getCurrentPath();
		CompilationUnitTree compileTree = path.getCompilationUnit();
		SourcePositions sp = arg1.getSourcePositions();
		long startIndex = sp.getStartPosition(compileTree, arg0);
		long currentLineNumber = compileTree.getLineMap().getLineNumber(startIndex);

		if (currentLineNumber == this.targetLineNumber) {
			getDataType();
		}
		return super.visitTry(arg0, arg1);
	}

	@Override
	public Object visitTypeParameter(TypeParameterTree arg0, Trees arg1) {
		TreePath path = getCurrentPath();
		CompilationUnitTree compileTree = path.getCompilationUnit();
		SourcePositions sp = arg1.getSourcePositions();
		long startIndex = sp.getStartPosition(compileTree, arg0);
		long currentLineNumber = compileTree.getLineMap().getLineNumber(startIndex);

		if (currentLineNumber == this.targetLineNumber) {
			getDataType();
		}
		return super.visitTypeParameter(arg0, arg1);
	}

	@Override
	public Object visitWhileLoop(WhileLoopTree arg0, Trees arg1) {
		TreePath path = getCurrentPath();
		CompilationUnitTree compileTree = path.getCompilationUnit();
		SourcePositions sp = arg1.getSourcePositions();
		long startIndex = sp.getStartPosition(compileTree, arg0);
		long currentLineNumber = compileTree.getLineMap().getLineNumber(startIndex);

		if (currentLineNumber == this.targetLineNumber) {
			getDataType();
		}
		return super.visitWhileLoop(arg0, arg1);
	}

	private void getDataType() {
		String dataType = "";
		boolean searched = false;

		for (int i = listOfVariables.size() - 1; i >= 0 && searched == false; i--) {
			if (listOfVariables.get(i).getVariableName().equals(variableName) && listOfVariables.get(i).getLineNumber() <= this.targetLineNumber && listOfVariables.get(i).getMethod().equals(this.currentMethod)) {
				dataType = listOfVariables.get(i).getType();
				searched = true;
			}
		}

		if (searched == false) {
			for (int i = listOfVariables.size() - 1; i >= 0; i--) {
				if (listOfVariables.get(i).getMethod().equals("class") && listOfVariables.get(i).getVariableName().equals(variableName) && listOfVariables.get(i).getLineNumber() <= this.targetLineNumber) {
					dataType = listOfVariables.get(i).getType();
					searched = true;
				}
			}
		}

		this.dataType = dataType;
	}

	public String getType() {
		return this.dataType;
	}

	public class Variable {
		private String variableName;
		private String type;
		private String method;
		private long lineNumber;

		public long getLineNumber() {
			return lineNumber;
		}

		public void setLineNumber(long lineNumber) {
			this.lineNumber = lineNumber;
		}

		public String getMethod() {
			return method;
		}

		public void setMethod(String method) {
			this.method = method;
		}

		public String getVariableName() {
			return variableName;
		}

		public void setVariableName(String variableName) {
			this.variableName = variableName;
		}

		public String getType() {
			return type;
		}

		public void setType(String type) {
			this.type = type;
		}

	}
}
